<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>      
        // 普通的function是在服务器里构造好了，直接传数据就行，匿名函数new Function（赋值给一个变量）实现了灵活性 或立即执行表达式）
        // new Function 使不仅仅可以只穿数据 还可以传行为（函数体）
        setTimeout(function(){
            console.log("hello world");
        },2000)

        var foo = function(){
            console.log("this:",this); //this是window
        }
        setTimeout(foo,2000);

        var name = "Lily";
        var obj = {
            name:"Jack",
            show:function(){
                console.log(this.name);
            }
        }
        setTimeout(obj.show,2000); //Lily; obj.show 没有调用show(),只是输出这个属性
        // 因此不知道是谁调用，默认为浏览器的window全局调用。 this指向的问题
        obj.show() //Jack 此时是被obj这个对象调用。
        window.name; //运行的时候浏览器默认给我们构造的对象 Lily
        Window.name; //宿主提供的对象构造器 类 window

        typeof Math;
        // 'object'
        typeof Array;
        // 'function'
        typeof Object;
        // 'function'
        typeof Function;
        //  'function'
        typeof JSON;
        // 'object'

        function add(a,b){
            return a+b;
        }
        console.log(add.name);
        console.log(add.length);
        console.log(add.prototype);
        console.log(add.prototype === add.__proto__);  //false
        var a = new add();
        add.prototype === a.__proto__;  //true
        // prototype表示该函数的原型，也表示一个类的成员的集合。
        // 对象的__proto__更准确的理解为“这个对象的构造器的原型属性”，
        // 同时也是构造器实例化出来的对象的原型对象

        // call方法
        //函数对象方法之 call
        //call([thisObj[, arg1[, arg2[, [, argN]]]]])
        //它允许您将函数的 this 对象从初始上下文变为由 thisObj 指定的新对象。
        // 如果没有提供 thisObj 参数，则 global 对象被用作 thisObj。
        // 与apply方法不同的地方是，apply的第二个参数类型必须是Array，
        // 而call方法是将所有的参数列举出来，用逗号分隔
        var name = "Jack";
        var tom  = {
            name: "Tom",
            sayName:function(){
                console.log(this.name);
            }
        };
        tom.sayName();
        var lily = {
            name:"Lily"
        };
        //想在不同的上下文对象（tom 和 lily）中重复使用函数 sayName()
        tom.sayName.call(lily);
        tom.sayName.call(window);
        // 利用call实现this的灵活调用
        // 语法： fn.call(thisObj，arg1，arg2，...)
        // 参数： arg1,arg2,...：被调用函数的实参
        // thisObj：将函数对象中的 this 指向 thisObj 对象  

        // swim fly案例 使用call
        function swim(m,n){
            console.log("i'm:"+this.name+" i can swim ___",m,n);
        }
        var bird = {
            name:"polly",
            fly:function(m,n){
                console.log("i'm:"+this.name+" i can fly ___",m,n);
            }
        };
        var me = {
            name:"Jack"
        };
        swim(1,2); //i'm: i can swim ___ 1 2
        swim.call(me,3,4); //i'm:Jack i can swim ___ 3 4
        bird.fly(5,6); //i'm:polly i can fly ___ 5 6
        bird.fly.call(me,7,8); //i'm:Jack i can fly ___ 7 8

        // Array.prototype.slice.call(arguments)
        // 调用数组原型的分割方法 利用call 可以 arguments 使用slice方法

        var foo = function(a,b,c,d,e){
            // arguments.slice(2); 报错
            var a = Array.prototype.slice.call(arguments,1,2); //从第2（1+1）个参数到第2个参数 数组下标为1 到2-1；
            console.log("a:",a);
        }
        foo(1,2,3,4,5);

        //函数对象方法之 apply
        //functionName.apply([thisObj[,argArray]])
        //与call方法不同的地方是，apply的第二个参数类型必须是Array
        swim.apply(me,[9,10]);
        bird.fly.apply(me,[11,12]);

        //思考：下列代码输出结果是多少：
        var a = 12;
        function add(a, b) {
            //需要两个参数
            return this.a + a + b;
        }
        var obj = { a: 23 }
        console.log(add(1, 2));//输出多少？ 15
        console.log(add.call(obj, 1, 2));//输出多少？ 26
        // 改成字符串 1212  2312
        
        var x = 45;
        var obj = {
            x:23,
            test:function(){
                function foo(){
                    console.log(this.x); //this 指向window this不传递
                }
                foo();//输出多少？45 this指向window
                // 想要解决
                // 1.利用bind
                var fee = foo.bind(this);  //this 指向obj,obj调用的test()
                fee();//输出多少？ 23
                foo.bind(this)();  //与上边两行效果一样，执行绑定后的新函数 23
                // bind不立即执行 需要调用指行即+()
                // 2.利用call
                foo.call(this); //23
            }
        };
        obj.test();

        var x = 45;
        var obj = {
            x:23,
            test:function(){
                function foo(){
                console.log(this.x);
                }

                var fee = foo.bind(this); 
                // fee();//输出多少？
                //foo.bind(this)();与上边两行效果一样，执行绑定后的新函数
                setTimeout(fee,2000);
                foo();//输出多少？
            }
        };
        obj.test();
        // 在obj.test();下面写:
        // 以下写法都不对 报错
        // foo.call(this)
        // obj.test.call(this)
        // obj.test.foo.call(this)

        // PPT上一个例子
        var x = 100;
        var obj = {
            x: 50
        };
        var foo = {
            x: 0,
            getX: function () {
                return this.x;
            }
        };

        //通过对象调用方法
        console.log(foo.getX()); //0

        //call()、apply()改变调用方法中this的指向为指定对象
        console.log(foo.getX.call(obj)); //50
        console.log(foo.getX.apply(obj)); //50

        //call()、apply()没有指定对象时 默认指向全局对象（window）
        console.log(foo.getX.call()); //100
        console.log(foo.getX.apply()); //100

        //call()、apply()使用仍然是执行原来对象的方法里面的代码，
        //只是代码中的this指向改变了
        //如果调用的对象方法里面没有this，
        //那么使用call()和apply()没有任何改变，也没有意义

        //bind 参数的问题
        // Define the original function with four parameters.
        var displayArgs = function (val1, val2, val3, val4) {
            console.log(val1 + " " + val2 + " " + val3 + " " + val4);
        };
        var emptyObject = {};
        // Create a new function that uses the 12 and "a" parameters
        // as the first and second parameters.
        var displayArgs2 = displayArgs.bind(emptyObject, 12, "a");
        // Call the new function. The "b" and "c" parameters are used
        // as the third and fourth parameters.
        displayArgs2("b", "c");// Output: 12 a b c
        // 该绑定函数将 bind 方法中指定的参数用作第一个参数和第二个参数。
        // 在调用该绑定函数时，指定的任何参数将用作第三个、第四个参数（依此类推）
    </script>
</body>
</html>